#include "src/net/inet_address.h"

#include <assert.h>
#include <netdb.h>
#include <netinet/in.h>

#include "src/net/endian.h"

static constexpr in_addr_t kInaddrAny = INADDR_ANY;
static constexpr in_addr_t kInaddrLoopback = INADDR_LOOPBACK;

namespace fio66::net {
static_assert(sizeof(InetAddress) == sizeof(struct sockaddr_in6),
              "InetAddress is same size as sockaddr_in6");
static_assert(offsetof(sockaddr_in, sin_family) == 0, "sin_family offset 0");
static_assert(offsetof(sockaddr_in6, sin6_family) == 0, "sin6_family offset 0");
static_assert(offsetof(sockaddr_in, sin_port) == 2, "sin_port offset 2");
static_assert(offsetof(sockaddr_in6, sin6_port) == 2, "sin6_port offset 2");

InetAddress::InetAddress(uint16_t port, bool loopback_only, bool ipv6) {
  static_assert(offsetof(InetAddress, addr6_) == 0, "addr6_ offset 0");
  static_assert(offsetof(InetAddress, addr_) == 0, "addr_ offset 0");
  if (ipv6) {
    ZeroMemory(addr6_) addr6_.sin6_family = AF_INET6;
    in6_addr ip = loopback_only ? in6addr_loopback : in6addr_any;
    addr6_.sin6_addr = ip;
    addr6_.sin6_port = sockets::HostToNetwork16(port);
  } else {
    ZeroMemory(addr_);
    addr_.sin_family = AF_INET;
    in_addr_t ip = loopback_only ? kInaddrLoopback : kInaddrAny;
    addr_.sin_addr.s_addr = sockets::HostToNetwork32(ip);
    addr_.sin_port = sockets::HostToNetwork16(port);
  }
}

/// Constructs an endpoint with given ip and port.
/// @c ip should be "1.2.3.4"
InetAddress::InetAddress(std::string_view ip, uint16_t port, bool ipv6) {
  if (ipv6 || strchr(ip.data(), ':')) {
    ZeroMemory(addr6_);
    sockets::FromIpPort(ip.data(), port, &addr6_);
  } else {
    ZeroMemory(addr_);
    sockets::FromIpPort(ip.data(), port, &addr_);
  }
}

std::string InetAddress::ToIp() const {
  char buf[64] = {0};
  sockets::ToIp(buf, sizeof buf, GetSockAddr());
  return buf;
}

std::string InetAddress::ToIpPort() const {
  char buf[64] = {0};
  sockets::ToIpPort(buf, sizeof buf, GetSockAddr());
  return buf;
}

uint16_t InetAddress::Port() const {
  return sockets::NetworkToHost16(PortNetEndian());
}

uint32_t InetAddress::IPV4NetEndian() const {
  assert(Family() == AF_INET);
  return addr_.sin_addr.s_addr;
}

static __thread char t_resolveBuffer[64 * 1024];
// resolve hostname to IP address, not changing port or sin_family
// return true on success.
// thread safe
bool InetAddress::Resolve(std::string_view hostname, InetAddress* result) {
  assert(result != NULL);
  struct hostent hent;
  struct hostent* he = NULL;
  int herrno = 0;
  ZeroMemory(hent);

  int ret = gethostbyname_r(hostname.data(), &hent, t_resolveBuffer,
                            sizeof t_resolveBuffer, &he, &herrno);
  if (ret == 0 && he != NULL) {
    assert(he->h_addrtype == AF_INET && he->h_length == sizeof(uint32_t));
    result->addr_.sin_addr = *reinterpret_cast<struct in_addr*>(he->h_addr);
    return true;
  } else {
    if (ret) {
      LOGE << "InetAddress::resolve";
    }
    return false;
  }
}
// static std::vector<InetAddress> resolveAll(const char* hostname, uint16_t
// port = 0){}

// set IPv6 ScopeID
void InetAddress::SetScopeId(uint32_t scope_id) {
  if (Family() == AF_INET6) {
    addr6_.sin6_scope_id = scope_id;
  }
}

}  // namespace fio66::net
